package rtu

import (
	"fmt"
	"time"
	"tym/modbus"
)

type Option func(*options)

type options struct {
	Name        string
	Baud        int
	ReadTimeout time.Duration // Total timeout
	Size        byte
}

func WithName(name string) Option {
	return func(o *options) {
		o.Name = name
	}
}

func WithBaud(baud int) Option {
	return func(o *options) {
		o.Baud = baud
	}
}
func WithReadTimeout(timeout time.Duration) Option {
	return func(o *options) {
		o.ReadTimeout = timeout
	}
}

func WithSize(size byte) Option {
	return func(o *options) {
		o.Size = size
	}
}

const (
	// ModbusRTULength RTU 请求包长度
	ModbusRTULength = 8
)

// RtuProtocolDataUnit rtu 包
// slaveId 1 字节
// functionCode 1字节
// address 2字节
// complex 复合字段, 读： 数量、 写：值
type RtuProtocolDataUnit struct {
	SlaveId      uint8
	FunctionCode uint8
	Address      uint16
	Complex      uint16
}

func Pack(slaveId, functionCode, address, complex uint16) *RtuProtocolDataUnit {
	packer := RtuProtocolDataUnit{
		SlaveId:      uint8(slaveId),
		FunctionCode: uint8(functionCode),
		Address:      address,
		Complex:      complex,
	}

	return &packer
}

func (p RtuProtocolDataUnit) Encode() []byte {
	requestData := []byte{
		p.SlaveId,
		p.FunctionCode,
		byte(p.Address >> 8),
		byte(p.Address & 0xFF),
		byte(p.Complex >> 8),
		byte(p.Complex & 0xFF),
	}
	crc := p.crc16(requestData)
	requestData = append(requestData, byte(crc&0xFF), byte(crc>>8))

	return requestData
}

func (p *RtuProtocolDataUnit) Decode(adu []byte) []uint16 {

	if len(adu) < 7 {
		return nil
	}

	// 获取主要数据位置
	var data []byte
	switch p.FunctionCode {
	case modbus.FuncCodeReadHoldingRegisters:
		// 0 站号 1
		// 1 功能码 1
		// 2 数据字节数 1
		// length - 2 ~ length - 1: 冗余校验
		data = adu[3 : len(adu)-2]
	case modbus.FuncCodeWriteHoldingRegisters:
		// 06 功能码 1 站号 1 功能码 2 寄存器地址 2 数值
		data = adu[4 : len(adu)-2]
	}

	values := make([]uint16, len(data)/2)
	for i := 0; i < len(values); i++ {
		values[i] = uint16(data[2*i])<<8 | uint16(data[2*i+1])
	}

	return values
}

func (p RtuProtocolDataUnit) Verify(req []byte, res []byte) error {
	if len(res) < 7 {
		return fmt.Errorf("respone length error")
	}
	// 先校验CRC
	resCrc := uint16(res[len(res)-2]) | uint16(res[len(res)-1])<<8
	if resCrc != p.crc16(res[:len(res)-2]) {
		return fmt.Errorf("crc check failed")
	}
	if req[0] != res[0] {
		return fmt.Errorf("请求 slave id 与 返回 slave id 错误")
	}

	return nil
}

func (p RtuProtocolDataUnit) crc16(data []byte) uint16 {
	var crc uint16 = 0xFFFF
	for _, b := range data {
		crc ^= uint16(b)
		for i := 0; i < 8; i++ {
			if (crc & 1) != 0 {
				crc = (crc >> 1) ^ 0xA001
			} else {
				crc >>= 1
			}
		}
	}
	return crc
}
